/**
 * Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "GPUImageBrightnessFilter.h"
#include "GPUImageColorInvertFilter.h"
#include "GPUImageContrastFilter.h"
#include "GPUImageExposureFilter.h"
#include "GPUImageFalseColorFilter.h"
#include "GPUImageGammaFilter.h"
#include "GPUImageGrayscaleFilter.h"
#include "GPUImageHighlightShadowFilter.h"
#include "GPUImageHueFilter.h"
#include "GPUImageLevelsFilter.h"
#include "GPUImageMonochromeFilter.h"
#include "GPUImagePixelationFilter.h"
#include "GPUImageRGBFilter.h"
#include "GPUImageSaturationFilter.h"
#include "GPUImageSepiaToneFilter.h"
#include "GPUImageSharpenFilter.h"
#include "GPUImageTransformFilter.h"
#include "GPUImageWhiteBalanceFilter.h"
#include "GPURender.h"
#include "NapiManager.h"
#include "napi/native_api.h"
#include "napi_tool.h"
#include "napi_util.h"
#include <bits/alltypes.h>

static napi_value SetImage(napi_env env, napi_callback_info info)
{
    return nullptr;
}

static napi_value SetFilterFrameListener(napi_env env,
                                         napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_ref ref;
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
    NAPI_CALL(env, napi_create_reference(env, args[0], 1, &ref));
    std::shared_ptr<CallbackListener> callbackListener =
        std::make_shared<CallbackListener>(env, ref);
    if (!IsMatchType(env, args[0], napi_function)) {
        ThrowErr(env, INVALID_CALLBACK, "Wrong argument type, should be function");
        return nullptr;
    }
    GPURender::getInstance()->setCallback(callbackListener);
    return nullptr;
}

static napi_value SwitchFilter(napi_env env, napi_callback_info info)
{
    size_t argc = 7;
    napi_value args[7] = {nullptr};
    std::string filter;
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
    GetStringValue(env, args[Number::ZEROTH], filter);
    std::shared_ptr<GPUImageFilter> pFilter = nullptr;
    if ("GPUImageContrastFilter" == filter) {
        double contrast;
        NAPI_CALL(env, napi_get_value_double(env, args[Number::FIRST], &contrast));
        pFilter = std::make_shared<GPUImageContrastFilter>(contrast);
    } else if ("GPUImageColorInvertFilter" == filter) {
        pFilter = std::make_shared<GPUImageColorInvertFilter>();
    } else if ("GPUImagePixelationFilter" == filter) {
        double pixel;
        NAPI_CALL(env, napi_get_value_double(env, args[Number::FIRST], &pixel));
        std::shared_ptr<GPUImagePixelationFilter> filter =
            std::make_shared<GPUImagePixelationFilter>();
        GPURender::getInstance()->runOnDraw([filter, pixel]() {
            filter->setPixel(pixel);
        });
        pFilter = filter;
    } else if ("GPUImageHueFilter" == filter) {
        double hue;
        NAPI_CALL(env, napi_get_value_double(env, args[Number::FIRST], &hue));
        pFilter = std::make_shared<GPUImageHueFilter>(hue);
    } else if ("GPUImageBrightnessFilter" == filter) {
        double brightness;
        NAPI_CALL(env,
                  napi_get_value_double(env, args[Number::FIRST], &brightness));
        pFilter = std::make_shared<GPUImageBrightnessFilter>(brightness);
    } else if ("GPUImageGrayscaleFilter" == filter) {
        pFilter = std::make_shared<GPUImageGrayscaleFilter>();
    } else if ("GPUImageSepiaToneFilter" == filter) {
        double sepia;
        NAPI_CALL(env, napi_get_value_double(env, args[Number::FIRST], &sepia));
        pFilter = std::make_shared<GPUImageSepiaToneFilter>(sepia);
    } else if ("GPUImageSharpenFilter" == filter) {
        double sharpen;
        NAPI_CALL(env, napi_get_value_double(env, args[Number::FIRST], &sharpen));
        pFilter = std::make_shared<GPUImageSharpenFilter>(sharpen);
    } else if ("GPUImageSaturationFilter" == filter) {
        double saturation;
        NAPI_CALL(env,
                  napi_get_value_double(env, args[Number::FIRST], &saturation));
        pFilter = std::make_shared<GPUImageSaturationFilter>(saturation);
    } else if ("GPUImageLevelsFilter" == filter) {
        std::vector<double> redMin = GetLevelsArray(env, args[Number::FIRST]);
        std::vector<double> greenMin = GetLevelsArray(env, args[Number::SECOND]);
        std::vector<double> blueMin = GetLevelsArray(env, args[Number::THIRD]);
        if (!redMin.empty() || !greenMin.empty() || !blueMin.empty()) {
            std::shared_ptr<GPUImageLevelsFilter> filter =
                std::make_shared<GPUImageLevelsFilter>();
            GPURender::getInstance()->runOnDraw([filter, redMin, greenMin,
                                                 blueMin]() {
                filter->setRedMin(
                    redMin[ColorLevelIndex::MIN], redMin[ColorLevelIndex::MID],
                    redMin[ColorLevelIndex::MAX], redMin[ColorLevelIndex::MIN_OUT],
                    redMin[ColorLevelIndex::MAX_OUT]);
                filter->setGreenMin(
                    greenMin[ColorLevelIndex::MIN], greenMin[ColorLevelIndex::MID],
                    greenMin[ColorLevelIndex::MAX], greenMin[ColorLevelIndex::MIN_OUT],
                    greenMin[ColorLevelIndex::MAX_OUT]);
                filter->setBlueMin(
                    blueMin[ColorLevelIndex::MIN], blueMin[ColorLevelIndex::MID],
                    blueMin[ColorLevelIndex::MAX], blueMin[ColorLevelIndex::MIN_OUT],
                    blueMin[ColorLevelIndex::MAX_OUT]);
            });
            pFilter = filter;
        } else {
            LOGE("error input levels filter param!");
            return nullptr;
        }
    } else if ("GPUImageExposureFilter" == filter) {
        double exposure;
        NAPI_CALL(env, napi_get_value_double(env, args[Number::FIRST], &exposure));
        pFilter = std::make_shared<GPUImageExposureFilter>(exposure);
    } else if ("GPUImageRGBFilter" == filter) {
        double red;
        double green;
        double blue;
        NAPI_CALL(env, napi_get_value_double(env, args[Number::FIRST], &red));
        NAPI_CALL(env, napi_get_value_double(env, args[Number::SECOND], &green));
        NAPI_CALL(env, napi_get_value_double(env, args[Number::THIRD], &blue));
        pFilter = std::make_shared<GPUImageRGBFilter>(red, green, blue);
    } else if ("GPUImageWhiteBalanceFilter" == filter) {
        double temperature;
        double tint;
        NAPI_CALL(env,
                  napi_get_value_double(env, args[Number::FIRST], &temperature));
        NAPI_CALL(env, napi_get_value_double(env, args[Number::SECOND], &tint));
        pFilter = std::make_shared<GPUImageWhiteBalanceFilter>(temperature, tint);
    } else if ("GPUImageMonochromeFilter" == filter) {
        double intensity;
        double red;
        double green;
        double blue;
        double alpha;
        NAPI_CALL(env, napi_get_value_double(env, args[Number::FIRST], &intensity));
        NAPI_CALL(env, napi_get_value_double(env, args[Number::SECOND], &red));
        NAPI_CALL(env, napi_get_value_double(env, args[Number::THIRD], &green));
        NAPI_CALL(env, napi_get_value_double(env, args[Number::FOURTH], &blue));
        NAPI_CALL(env, napi_get_value_double(env, args[Number::FIFTH], &alpha));
        float rgba[4] = {static_cast<float>(red), static_cast<float>(green),
                         static_cast<float>(blue), static_cast<float>(alpha)};
        pFilter = std::make_shared<GPUImageMonochromeFilter>(intensity, rgba);
    } else if ("GPUImageFalseColorFilter" == filter) {
        double fRed;
        double fGreen;
        double fBlue;
        double sRed;
        double sGreen;
        double sBlue;
        NAPI_CALL(env, napi_get_value_double(env, args[Number::FIRST], &fRed));
        NAPI_CALL(env, napi_get_value_double(env, args[Number::SECOND], &fGreen));
        NAPI_CALL(env, napi_get_value_double(env, args[Number::THIRD], &fBlue));
        NAPI_CALL(env, napi_get_value_double(env, args[Number::FOURTH], &sRed));
        NAPI_CALL(env, napi_get_value_double(env, args[Number::FIFTH], &sGreen));
        NAPI_CALL(env, napi_get_value_double(env, args[Number::SIXTH], &sBlue));
        pFilter = std::make_shared<GPUImageFalseColorFilter>(fRed, fGreen, fBlue,
                                                             sRed, sGreen, sBlue);
    } else if ("GPUImageTransformFilter" == filter) {
        pFilter = std::make_shared<GPUImageTransformFilter>();
    } else if ("GPUImageGammaFilter" == filter) {
        double gamma;
        NAPI_CALL(env, napi_get_value_double(env, args[Number::FIRST], &gamma));
        pFilter = std::make_shared<GPUImageGammaFilter>(gamma);
    } else if ("GPUImageHighlightShadowFilter" == filter) {
        double shadows;
        double highlights;
        NAPI_CALL(env, napi_get_value_double(env, args[Number::FIRST], &shadows));
        NAPI_CALL(env,
                  napi_get_value_double(env, args[Number::SECOND], &highlights));
        pFilter =
            std::make_shared<GPUImageHighlightShadowFilter>(shadows, highlights);
    } else {
        pFilter = std::make_shared<GPUImageFilter>();
    }
    if (pFilter) {
        GPURender::getInstance()->setFilter(pFilter);
    }
    return nullptr;
}

static napi_value SavePixelMap(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_ref ref;
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
    NAPI_CALL(env, napi_create_reference(env, args[Number::ZEROTH], 1, &ref));
    std::shared_ptr<CallbackListener> callbackListener =
        std::make_shared<CallbackListener>(env, ref);
    if (!IsMatchType(env, args[Number::ZEROTH], napi_function)) {
        ThrowErr(env, INVALID_CALLBACK, "Wrong argument type, should be function");
        return nullptr;
    }
    GPURender::getInstance()->savePixelMap(callbackListener);
    return nullptr;
}

static napi_value GetSurfaceId(napi_env env, napi_callback_info info)
{
    napi_value result = nullptr;
    uint64_t surfaceId = GPURender::getInstance()->getSurfaceId();
    NAPI_CALL(env, napi_create_int64(env, surfaceId, &result));
    return result;
}

static napi_value ReleaseRenderThread(napi_env env, napi_callback_info info)
{
    GPURender::getInstance()->releaseRenderThread();
    return nullptr;
}

static napi_value DumpImage(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
    std::string path;
    GetStringValue(env, args[0], path);
    GPURender::getInstance()->dumpImage(path);
    return nullptr;
}

static napi_value StartRenderThread(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    std::string id;
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
    GetStringValue(env, args[0], id);
    NapiManager::GetInstance()->StartRenderThread(id);
    return nullptr;
}

static napi_value SetRotation(napi_env env, napi_callback_info info)
{
    size_t argc = 3;
    napi_value args[3] = {nullptr};
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
    int32_t orientation;
    bool flipX;
    bool flipY;
    NAPI_CALL(env, napi_get_value_int32(env, args[Number::ZEROTH], &orientation));
    NAPI_CALL(env, napi_get_value_bool(env, args[Number::FIRST], &flipX));
    NAPI_CALL(env, napi_get_value_bool(env, args[Number::SECOND], &flipY));
    Rotation rotation = static_cast<Rotation>(rotation);
    GPURender::getInstance()->setOrientation(rotation, flipX, flipY);
    return nullptr;
}

EXTERN_C_START static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        DECLARE_NAPI_FUNCTION("setImage", SetImage),
        DECLARE_NAPI_FUNCTION("setFilterFrameListener", SetFilterFrameListener),
        DECLARE_NAPI_FUNCTION("switchFilter", SwitchFilter),
        DECLARE_NAPI_FUNCTION("savePixelMap", SavePixelMap),
        DECLARE_NAPI_FUNCTION("getSurfaceId", GetSurfaceId),
        DECLARE_NAPI_FUNCTION("releaseRenderThread", ReleaseRenderThread),
        DECLARE_NAPI_FUNCTION("dumpImage", DumpImage),
        DECLARE_NAPI_FUNCTION("startRenderThread", StartRenderThread),
        DECLARE_NAPI_FUNCTION("setRotation", SetRotation),
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    bool ret = NapiManager::GetInstance()->Export(env, exports);
    if (!ret) {
        LOGE("get XComponent Obj failed");
    }
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "gpuimagenative",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
    napi_module_register(&demoModule);
}